/*
 * MIT License
 *
 * Copyright (c) 2024-2025 milkpotatoes
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

const global = globalThis as { [index: string]: any };
const ILLEGAL_INVOCATION = 'Illegal invocation';

declare interface InnerEncoderUtils {
    _innerBinding: (that: TextEncoder) => void;
    _innerEncode: (that: TextEncoder, str: string) => ArrayBuffer;
    _innerEncodeInto: (that: TextEncoder, str: string, buffer: ArrayBuffer) => number[];
}

const { _innerBinding: innerBinding,
    _innerEncode: innerEncode,
    _innerEncodeInto: innerEncodeInto } = global._innerEncoderUtils as InnerEncoderUtils;
delete global._innerDecoderUtils;

declare interface TextEncoderResult {
    read: number;
    written: number;
}

function typeCheck(instance: Object, constructor: Object): void {
    if (!(instance instanceof (constructor as any))) {
        throw new TypeError(ILLEGAL_INVOCATION);
    }
}

class TextEncoder {
    encode(str: string): Uint8Array {
        typeCheck(this, TextEncoder);
        if (arguments.length < 1) {
            throw RangeError(`Failed to execute 'encode' on TextEncoder: ` +
                `1 argument required, but only ${arguments.length} present.`);
        }
        if (typeof str != 'string') str = String(str);

        if (str.length === 0) return new Uint8Array(0);
        const result = innerEncode(this, str);
        if (result === undefined) {
            throw new RangeError('Failed to execute \'encode\' on TextEncoder.')
        }
        return new Uint8Array(result);
    }
    encodeInto(str: string, buffer: Uint8Array): TextEncoderResult {
        typeCheck(this, TextEncoder);
        if (arguments.length < 2) {  // 2: required args number
            throw RangeError(`Failed to execute 'encodeInto' on TextEncoder: ` +
                `2 arguments required, but only ${arguments.length} present.`);
        }
        if (typeof str != 'string') {
            throw new TypeError("Failed to execute 'encodeInto' on TextEncoder: " +
                "Invalid arguments, paramter str must be string, but " + typeof str + " is provided");
        }
        if (!(buffer instanceof Uint8Array) &&
            !((buffer as any) instanceof Uint8ClampedArray)) {
            throw new TypeError("Failed to execute 'encodeInto' on TextEncoder: " +
                "Invalid arguments, paramter buffer must be Uint8Array, but " + typeof str + " is provided");
        }
        const result = innerEncodeInto(this, str, buffer.buffer);
        if (result === undefined) {
            throw RangeError('Failed to execute \'encodeInto\' on TextEncoder.')
        }
        return { read: result[0], written: result[1] };
    }
    get encoding() { return 'utf-8'; }
}

export default TextEncoder;

Object.defineProperties(globalThis, {
    'TextEncoder': { value: TextEncoder, enumerable: false, configurable: true, writable: true },
});
